package org.ws.httphelper.support.pipeline.handler.request;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ws.httphelper.common.PropertyUtil;
import org.ws.httphelper.core.pipeline.HandlerOrder;
import org.ws.httphelper.core.pipeline.RequestHandler;
import org.ws.httphelper.exception.RequestException;
import org.ws.httphelper.model.RequestContext;
import org.ws.httphelper.model.config.RequestConfig;
import org.ws.httphelper.model.field.ParamField;
import org.ws.httphelper.model.http.ContentType;
import org.ws.httphelper.model.http.HttpMethod;
import org.ws.httphelper.model.http.RequestData;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * 生成请求对应的body
 */
@HandlerOrder(value = 3,level = HandlerOrder.OrderLevel.SYSTEM)
public class BuildBodyHandler implements RequestHandler {

    private static Logger log = LoggerFactory.getLogger(BuildBodyHandler.class.getName());

    private static Map<ContentType, Function<RequestContext,Object>> methodBodyFunctions = new ConcurrentHashMap<>();

    static {
        methodBodyFunctions.put(ContentType.RAW_TEXT, context -> String.valueOf(context.getInput()));
        methodBodyFunctions.put(ContentType.RAW_HTML, context -> String.valueOf(context.getInput()));
        methodBodyFunctions.put(ContentType.RAW_JSON, context -> {
            Object input = context.getInput();
            if(input instanceof String){
                return input;
            }
            else {
                return JSON.toJSONString(input);
            }
        });
        methodBodyFunctions.put(ContentType.RAW_XML, context -> {
            Object input = context.getInput();
            if(input instanceof String){
                return input;
            }
            else {
                throw new RequestException("RAW_XML: please input xml String.");
            }
        });
        methodBodyFunctions.put(ContentType.X_WWW_FORM_URLENCODED, context -> {
            RequestConfig requestConfig = context.getRequestConfig();
            Object input = context.getInput();
            Map<String, ParamField> fields = requestConfig.getParamFields();
            if(input instanceof String){
                return input;
            }
            else if(input instanceof Map){
                StringBuilder builder = new StringBuilder();
                Map<String,Object> inputMap = (Map<String,Object>) input;
                if(MapUtils.isNotEmpty(fields)){
                    for (ParamField field : fields.values()) {
                        String fieldName = field.getFieldName();
                        Object defaultValue = field.getDefaultValue();
                        Object value = inputMap.get(fieldName);
                        if(value == null && defaultValue != null){
                            value = defaultValue;
                        }
                        builder.append(fieldName).append("=").append(value).append("&");
                    }
                }
                else {
                    for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
                        builder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
                    }
                }
                return builder.toString();
            }
            else {
                StringBuilder builder = new StringBuilder();
                Class<?> inClass = input.getClass();
                if(MapUtils.isNotEmpty(fields)){
                    for (ParamField field : fields.values()) {
                        String fieldName = field.getFieldName();
                        Object defaultValue = field.getDefaultValue();
                        try {
                            Object value = PropertyUtil.getValue(input,fieldName);
                            if(value == null && defaultValue != null){
                                value = defaultValue;
                            }
                            builder.append(fieldName).append("=").append(value).append("&");
                        } catch (Exception e) {
                            throw new RequestException(e.getMessage(),e);
                        }
                    }
                }
                else {
                    for (Field field : PropertyUtil.getFields(inClass)) {
                        final String fieldName = field.getName();
                        try {
                            Object value = PropertyUtil.getValue(input,fieldName);
                            builder.append(fieldName).append("=").append(value).append("&");
                        } catch (Exception e) {
                            throw new RequestException(e.getMessage(),e);
                        }
                    }
                }
                return builder.toString();
            }
        });
        methodBodyFunctions.put(ContentType.BINARY, context -> {
            Object input = context.getInput();
            if(input instanceof byte[]){
                return input;
            }
            else {
                File file;
                if(input instanceof String){
                    file = new File((String) input);
                }
                else if(input instanceof File){
                    file = (File)input;
                }
                else {
                    throw new RequestException("input can not parser to file.");
                }
                try {
                    return FileUtils.readFileToByteArray(file);
                } catch (IOException e) {
                    throw new RequestException(e.getMessage(),e);
                }
            }
        });
    }

    @Override
    public boolean handler(RequestContext requestContext) throws RequestException {
        // 根据定义生产body
        RequestData requestData = requestContext.getRequestData();
        RequestConfig requestConfig = requestContext.getRequestConfig();
        HttpMethod method = requestConfig.getMethod();
        ContentType contentType = requestData.getContentType();
        // 不需要body
        if(method == HttpMethod.GET || method == HttpMethod.HEAD
            || contentType == ContentType.EMPTY){
            return true;
        }
        Object body = methodBodyFunctions.get(contentType).apply(requestContext);
        requestData.setBody(body);

        if(log.isDebugEnabled() && body instanceof String){
            log.debug("build body:{}",body);
        }

        return true;
    }

}
